其他
原创 | 深入解析pe结构(上)
内存分配与文件读写
宏定义
正常例子:
#define TRUE 1
#define PI 3.1415926
#define FALSE 0
#include<stdio.h>
# define TEST 1*1+2
int main() {
printf("%d", 2*TEST+2);
}
例子:
#include<stdio.h>
# define MAX(A,B) ((A) > (B)?(A):(B))
int main() {
printf("%d", MAX(1,2));
}
头文件与重复包含问题
#include<stdio.h>
#include<windows.h>
#include"test.h"
int main() {
Function();
}
void Function() {
printf("hello world");
}
#include<stdio.h>
#include<windows.h>
#include"test.h"
void Function(); //添加一个空定义
int main() {
Function();
}
void Function() {
printf("hello world");
}
当x.h y.h两个头文件都包含了z.h头文件时,如果一个程序同时包含了x和y头文件,就相当于重复包含了两次z.h,这时候会引起编译报错
在新版visual studio code中创建头文件时会自动添加#pragma once来避免
在vc++6.0中,可以利用如下代码来避免
#if !defined(ZZZ) //其中zzz任意起名,越乱越好为了确保不会重复
#define ZZZ
中间是为了避免重复的代码
#endif
内存的分配释放
int* ptr;//声明指针
//在堆中申请内存,分配128个int
ptr = (int *)malloc(sizeof(int)*128);
//无论申请的空间大小 一定要进行校验 判断是否申请成功
if(ptr == NULL)
{
return 0;
}
//初始化分配的内存空间
memset(ptr,0,sizeof(int)*128);
//使用。。。
*(ptr) = 1;
//使用完毕 释放申请的堆空间
free(ptr);
//将指针设置为NULL
ptr = NULL;
文件读写练习
#include<windows.h>
#include<stdio.h>
int main() {
int size;
FILE* fp_in, *fp_out;
fp_in = fopen("C:\\Users\\11502\\Desktop\\feige.exe", "rb"); //读取二进制文件
if (fp_in == NULL) { //如果读取失败报错停止
printf("Fail to open file!\n");
exit(0);
}
fseek(fp_in, 0, SEEK_END); //将文件指针移向文件尾
size = ftell(fp_in); //计算文件指针离开头的位置,也就是计算文件的大小
rewind(fp_in); //将文件指针移动开头
printf("文件的大小为%d\n", size);
char* buffer = (char*)malloc(sizeof(char) * size); //根据文件的大小申请空间
if (buffer == NULL) { //如果申请失败报错
printf("Failed to allocate memory\n");
exit(0);
}
fread(buffer, size, 1, fp_in); //将读取的内容写入buffer内存
fclose(fp_in);
printf("在内存中的地址为%x\n", buffer);
fp_out = fopen("C:\\Users\\11502\\Desktop\\feige222.exe", "wb"); //申请文件指针
fwrite(buffer, size, 1, fp_out); //写入二进制文件
fclose(fp_out);
free(buffer);
return 0;
}
DOS头与PE头解析
pe文件结构图表
内存对齐与硬盘对齐
硬盘对齐:通常为 512 字节,也可以是 4KB。200h
内存对齐:在 x86 架构中通常是 4 字节,而在 x64 架构中通常是 8 字节。1000h
在内存中会比在硬盘中占据的空间大,pe文件在内存中大小会被拉伸
#include<stdio.h>
#include<windows.h>
int main() {
void* ptr = VirtualAlloc(NULL, 1, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
printf("%x\n", ptr);
system("pause");
return 0;
}
磁盘文件
DOS头
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic;
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
DWORD e_lfanew;
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; //5A 4D
WORD e_cblp; //00 90
WORD e_cp; //00 03
WORD e_crlc; //00 00
WORD e_cparhdr; //00 04
WORD e_minalloc; //00 00
WORD e_maxalloc; //FF FF
WORD e_ss; //00 00
WORD e_sp; //00 B8
WORD e_csum; //00 00
WORD e_ip; //00 00
WORD e_cs; //00 00
WORD e_lfarlc; //00 40
WORD e_ovno; //00 00
WORD e_res[4]; //因为是数组所以占8位 00 00 00 00 00 00 00 00
WORD e_oemid; //00 00
WORD e_oeminfo; //00 00
WORD e_res2[10]; //20个00
DWORD e_lfanew; //00 00 00 E8
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
NumberOfSections图中说是区块的数量,其实是节的数量,如果内容为3也就说明有3节,如下图在扩展pe头(OPTION PE)之后就是节
SizeOfHeaders所有头加节表的总大小,节表存储着每一节的属性信息
#include<stdio.h>
#include<windows.h>
LPVOID ReadPEFile(LPSTR lpszFile)
{
FILE* pFile = NULL;
DWORD fileSize = 0;
LPVOID pFileBuffer = NULL;
//打开文件
pFile = fopen(lpszFile, "rb");
if (!pFile)
{
printf(" 无法打开 EXE 文件! ");
return NULL;
}
//读取文件大小
fseek(pFile, 0, SEEK_END);
fileSize = ftell(pFile);
fseek(pFile, 0, SEEK_SET);
//分配缓冲区
pFileBuffer = malloc(fileSize);
if (!pFileBuffer)
{
printf(" 分配空间失败! ");
fclose(pFile);
return NULL;
}
//将文件数据读取到缓冲区
size_t n = fread(pFileBuffer, fileSize, 1, pFile);
if (!n)
{
printf(" 读取数据失败! ");
free(pFileBuffer);
fclose(pFile);
return NULL;
}
//关闭文件
fclose(pFile);
return pFileBuffer;
}
VOID PrintNTHeaders()
{
LPVOID pFileBuffer = NULL;
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
pFileBuffer = ReadPEFile((char*)"C:\\Users\\11502\\Desktop\\feige.exe");
if (!pFileBuffer)
{
printf("文件读取失败\n");
return;
}
//判断是否是有效的MZ标志
if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("不是有效的MZ标志\n");
free(pFileBuffer);
return;
}
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; //一个头的结构体对这个头的起始地址强制类型转换就相当于生成了这个头的结构体
//打印DOC头
printf("********************DOS头********************\n");
printf("MZ标志:%x\n", pDosHeader->e_magic);
printf("PE偏移:%x\n", pDosHeader->e_lfanew);
//判断是否是有效的PE标志
if (*((PDWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE) //由于e_lfanew是doword所以要转dword
{
printf("不是有效的PE标志\n");
free(pFileBuffer);
return;
}
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
//打印NT头
printf("********************NT头********************\n");
printf("NT:%x\n", pNTHeader->Signature);
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
printf("********************PE头********************\n");
printf("PE:%x\n", pPEHeader->Machine);
printf("节的数量:%x\n", pPEHeader->NumberOfSections);
printf("SizeOfOptionalHeader:%x\n", pPEHeader->SizeOfOptionalHeader);
//可选PE头
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
printf("********************OPTIOIN_PE头********************\n");
printf("OPTION_PE:%x\n", pOptionHeader->Magic);
//释放内存
free(pFileBuffer);
}
int main() {
PrintNTHeaders();
}
节表
c++联合体
struct Student
{
char 学号;
int 身份证号;
}
union Student
{
char 学号;
int 身份证号;
}
#include<stdio.h>
#include<windows.h>
union TestUnion{
char x;
int y;
};
int main() {
TestUnion test;
test.y = 0x12345678;
printf("%x\n", test.y);
printf("%x\n", test.x);
test.x = 0x99;
printf("%x\n", test.y);
system("pause");
}
节表结构
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
SizeOfRawData 节在文件中对齐后的尺寸
在之前程序加上这一段即可
//节表
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
printf("********************SECTION_PE头********************\n");
printf("OPTION_PE:%x\n", pSectionHeader->SizeOfRawData);
FileBuffer与ImageBuffer的相互转换(重要)
1.读取文件内容并申请内存将内容放到内存中(FileBuffer)
2.再申请一块内存存放ImageBuffer,申请的大小在扩展PE头中的SizeOfImage有记录
3.由图可知DOS头到节表的部分没有改变可以直接用SizeOfHeaders的大小的数据复制到内存中
4.根据NumberOfSections获取表的数量,写一个循环写入即可
memcpy((PVOID)((DWORD)pImageTemp + pSectionHeaderTemp->VirtualAddress), (PVOID)((DWORD)pFileBuffer + pSectionHeaderTemp->PointerToRawData), pSectionHeaderTemp->SizeOfRawData);
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
DWORD ToLoaderPE(LPSTR file_path, PVOID* pFileBuffer); //加载pe返回文件大小
DWORD CopyFileBufferToImageBuffer(PVOID pFileBuffer, PVOID* pImageBuffer); //FileBuffer转ImageBuffer
DWORD CopyImageBufferToNewFileBuffer(PVOID pImageBuffer, PVOID* pNewFileBuffer); //ImageBuffer转FileBuffer
BOOL MemoryToFile(PVOID pMemBuffer, DWORD size, LPSTR lpszFile); //FileBuffer存盘
char file_path[] = "C:\\Users\\11502\\Desktop\\feige.exe";
char write_file_path[] = "C:\\Users\\11502\\Desktop\\feige666.exe";
//返回PE文件大小
DWORD ToLoaderPE(LPSTR file_path, PVOID* pFileBuffer)
{
FILE* pFile = NULL;
DWORD FileSize = 0;
PVOID pFileBufferTemp = NULL;
pFile = fopen(file_path, "rb");
if (!pFile)
{
printf("(ToLoaderPE)Can't open file!\n");
return 0;
}
fseek(pFile, 0, SEEK_END);
FileSize = ftell(pFile);
printf("FileBuffer: %#x\n", FileSize);
fseek(pFile, 0, SEEK_SET);
pFileBufferTemp = malloc(FileSize);
if (!pFileBufferTemp)
{
printf("(ToLoaderPE)Allocate dynamic memory failed!\n");
fclose(pFile);
return 0;
}
DWORD n = fread(pFileBufferTemp, FileSize, 1, pFile);
if (!n)
{
printf("(ToLoaderPE)Read file failed!\n");
free(pFileBufferTemp);
fclose(pFile);
return 0;
}
*pFileBuffer = pFileBufferTemp;
pFileBufferTemp = NULL;
fclose(pFile);
return FileSize;
}
DWORD CopyFileBufferToImageBuffer(PVOID pFileBuffer, PVOID* pImageBuffer)
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
PVOID pImageTemp = NULL;
if (!pFileBuffer)
{
printf("(CopyFileBufferToImageBuffer)Can't open file!\n");
return 0;
}
if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("(CopyFileBufferToImageBuffer)No MZ flag, not exe file!\n");
return 0;
}
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
if (*((LPDWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("(CopyFileBufferToImageBuffer)Not a valid PE flag!\n");
return 0;
}
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
pImageTemp = malloc(pOptionHeader->SizeOfImage);
if (!pImageTemp)
{
printf("(CopyFileBufferToImageBuffer)Allocate dynamic memory failed!\n");
free(pImageTemp);
return 0;
}
memset(pImageTemp, 0, pOptionHeader->SizeOfImage);
memcpy(pImageTemp, pDosHeader, pOptionHeader->SizeOfHeaders); //这里的pDosHeader改成pFileBuffer也可以
PIMAGE_SECTION_HEADER pSectionHeaderTemp = pSectionHeader;
for (int n = 0; n < pPEHeader->NumberOfSections; n++, pSectionHeaderTemp++)
{
memcpy((PVOID)((DWORD)pImageTemp + pSectionHeaderTemp->VirtualAddress), (PVOID)((DWORD)pFileBuffer + pSectionHeaderTemp->PointerToRawData), pSectionHeaderTemp->SizeOfRawData); //将FileBuffer中的存储,复制到在ImageBuffer中的存储
printf("VirtualAddress%d: %#10x PointerToRawData%d: %#10x\n", n, (DWORD)pImageTemp + pSectionHeader->VirtualAddress, n, (DWORD)pFileBuffer + pSectionHeader->PointerToRawData);
}
*pImageBuffer = pImageTemp;
pImageTemp = NULL;
return pOptionHeader->SizeOfImage;
}
DWORD CopyImageBufferToNewFileBuffer(PVOID pImageBuffer, PVOID* pNewFileBuffer)
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
LPVOID pTempNewbuffer = NULL;
if (!pImageBuffer)
{
printf("(CopyImageBufferToNewBuffer)Can't open file!\n");
return 0;
}
if (*((PWORD)pImageBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("(CopyImageBufferToNewBuffer)No MZ flag, not exe file!\n");
return 0;
}
pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
if (*((PDWORD)((DWORD)pImageBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("(CopyImageBufferToNewBuffer)Not a valid PE flag!\n");
return 0;
}
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pImageBuffer + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4); // 这里必须强制类型转换
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
//获取new_buffer的大小
int new_buffer_size = pOptionHeader->SizeOfHeaders;
for (DWORD i = 0; i < pPEHeader->NumberOfSections; i++)
{
new_buffer_size += pSectionHeader[i].SizeOfRawData; // pSectionHeader[i]另一种加法
}
// 分配内存(newbuffer)
pTempNewbuffer = malloc(new_buffer_size);
if (!pTempNewbuffer)
{
printf("(CopyImageBufferToNewBuffer)Allocate dynamic memory failed!\n");
return 0;
}
memset(pTempNewbuffer, 0, new_buffer_size);
memcpy(pTempNewbuffer, pDosHeader, pOptionHeader->SizeOfHeaders);
// 循环拷贝节区
PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader;
for (DWORD j = 0; j < pPEHeader->NumberOfSections; j++, pTempSectionHeader++)
{//PointerToRawData节区在文件中的偏移,VirtualAddress节区在内存中的偏移地址,SizeOfRawData节在文件中对齐后的尺寸
memcpy((PDWORD)((DWORD)pTempNewbuffer + pTempSectionHeader->PointerToRawData), (PDWORD)((DWORD)pImageBuffer + pTempSectionHeader->VirtualAddress), pTempSectionHeader->SizeOfRawData);
}
//返回数据
*pNewFileBuffer = pTempNewbuffer; //暂存的数据传给参数后释放
pTempNewbuffer = NULL;
return new_buffer_size; // 返回计算得到的分配内存的大小
}
BOOL MemoryToFile(PVOID pMemBuffer, DWORD size, LPSTR lpszFile)
{
FILE* fp;
fp = fopen(lpszFile, "wb");
if (fp != NULL)
{
fwrite(pMemBuffer, size, 1, fp);
}
fclose(fp);
return 1;
}
VOID operate()
{
LPVOID pFileBuffer = NULL;
LPVOID pNewFileBuffer = NULL;
LPVOID pImageBuffer = NULL;
DWORD ret1 = ToLoaderPE(file_path, &pFileBuffer); // &pFileBuffer(void**类型) 传递地址对其值可以进行修改
printf("exe->filebuffer 返回值为计算所得文件大小:%#x\n", ret1);
DWORD ret2 = CopyFileBufferToImageBuffer(pFileBuffer, &pImageBuffer);
printf("filebuffer -> imagebuffer返回值为计算所得文件大小:%#x\n", ret2);
DWORD ret3 = CopyImageBufferToNewFileBuffer(pImageBuffer, &pNewFileBuffer);
printf("imagebuffer -> newfilebuffer返回值为计算所得文件大小:%#x\n", ret3);
MemoryToFile(pNewFileBuffer, ret3, write_file_path);
free(pFileBuffer);
free(pNewFileBuffer);
free(pImageBuffer);
}
int main()
{
operate();
getchar();
return 0;
}
RVA与FOA的转换
#include<windows.h>
#include<stdio.h>
#define FILE_PATH "文件地址"
int GetFileLength(FILE* pf, DWORD* Length)
{
int ret = 0;
fseek(pf, 0, SEEK_END);
*Length = ftell(pf);
fseek(pf, 0, SEEK_SET);
return ret;
}
int MyReadFile(void** pFileAddress)
{
int ret = 0;
DWORD Length = 0;
//打开文件
FILE* pf = fopen(FILE_PATH, "rb");
if (pf == NULL)
{
ret = -1;
printf("func ReadFile() Error!\n");
return ret;
}
//获取文件长度
ret = GetFileLength(pf, &Length);
if (ret != 0 && Length == -1)
{
ret = -2;
printf("func GetFileLength() Error!\n");
return ret;
}
//分配空间
*pFileAddress = (PVOID)malloc(Length);
if (*pFileAddress == NULL)
{
ret = -3;
printf("func malloc() Error!\n");
return ret;
}
memset(*pFileAddress, 0, Length);
//读取文件进入内存
fread(*pFileAddress, Length, 1, pf);
fclose(pf);
return ret;
}
int RVA_TO_FOA(PVOID FileAddress, DWORD RVA, PDWORD pFOA)
{
int ret = 0;
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)(FileAddress);
PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader + sizeof(IMAGE_FILE_HEADER));
PIMAGE_SECTION_HEADER pSectionGroup = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
//RVA在文件头中 或 SectionAlignment 等于 FileAlignment 时RVA等于FOA
if (RVA < pOptionalHeader->SizeOfHeaders || pOptionalHeader->SectionAlignment == pOptionalHeader->FileAlignment)
{
*pFOA = RVA;
return ret;
}
//循环判断RVA在节区中
for (int i = 0; i < pFileHeader->NumberOfSections; i++)
{
if (RVA >= pSectionGroup[i].VirtualAddress && RVA < pSectionGroup[i].VirtualAddress + pSectionGroup[i].Misc.VirtualSize)
{
*pFOA = pSectionGroup[i].PointerToRawData + RVA - pSectionGroup[i].VirtualAddress;
return ret;
}
}
//没有找到地址
ret = -4;
printf("func RAV_TO_FOA() Error: %d 地址转换失败!\n", ret);
return ret;
}
int main() {
PVOID pFileAddress;
DWORD FOA;
MyReadFile(&pFileAddress);
RVA_TO_FOA(pFileAddress, (DWORD)0x1080, &FOA);
printf("%x", FOA);
return 0;
}
代码节空白区添加代码
目的及思路分析
硬编码
#include<windows.h>
void function(){
MessageBoxA(0, 0, 0, 0);
}
int main() {
function();
return 0;
}
真正要跳转的地址 = E8这条指令的下一行地址 + X
X = 真正要跳转的地址 - E8这条指令的下一行地址
X = 要跳转的地址 - (E8的地址 + 5)
MessageBoxA的地址
修改PE文件
通过软件查看text表,表是从1000开始从46000(45000+1000)结束
X = 要跳转的地址 - (E8的地址 + 5)
使用jmp跳转到程序入口点AddressOfEntryPoint,000441EC X = 4441ec - 4450c2 FF FF F1 2A
对齐大小不同情况下的计算方法
oep的偏移地址就是Misc+shellcode的大小+virtualaddress
大家可以自行使用notepad进行测试
//func.c 函数的实现
#include <Windows.h>
#include<stdio.h>
#define FILEPATH "D:/ipmsg.exe"
#define FILEPATH_NEW "D:/ipmsg_new.exe"
DWORD ReadPEFile(IN LPSTR lpszFile, OUT LPVOID* pFileBuffer);
DWORD CopyFileBufferToImageBuffer(IN LPVOID pFileBuffer, OUT LPVOID* pImageBuffer);
DWORD CopyImageBufferToNewBuffer(IN LPVOID pImageBuffer, OUT LPVOID* pNewBuffer);
BOOL MemeryTOFile(IN LPVOID pMemBuffer, IN size_t size, OUT LPSTR lpszFile);
DWORD RvaToFileOffset(IN LPVOID pFileBuffer, IN DWORD dwRva);
VOID TestAddCodeIncodeSec();
//打印PE文件信息
VOID PrintNTHeaders();
#define FILEPATH_IN
#define FILEPATH_OUT
#define SHELLCODELENGTH 0x12 //添加代码长度
#define MESSAGEBOXADDR 0x754EF280 //执行代码的函数入口地址,就是实际要跳转的地址
//全局变量声明
BYTE shellCode[] =
{
0x6A,00,0x6A,00,0x6A,00,0x6A,00,
0xE8,00,00,00,00,
0xE9,00,00,00,00,
};
DWORD ReadPEFile(LPSTR lpszFile, OUT LPVOID* pFileBuffer)
{
FILE* pFile = NULL;
DWORD fileSize = 0;
//LPVOID pFileBuffer = NULL;
//打开文件
pFile = fopen(lpszFile, "rb");
if (!pFile)
{
printf(" 无法打开 EXE 文件! ");
return NULL;
}
//读取文件大小
fseek(pFile, 0, SEEK_END);
fileSize = ftell(pFile);
fseek(pFile, 0, SEEK_SET);
//分配缓冲区
*pFileBuffer = malloc(fileSize);
if (!(*pFileBuffer))
{
printf(" 分配空间失败! ");
fclose(pFile);
return NULL;
}
//将文件数据读取到缓冲区
size_t n = fread(*pFileBuffer, fileSize, 1, pFile);
if (!n)
{
printf(" 读取数据失败! ");
free(*pFileBuffer);
fclose(pFile);
return NULL;
}
//关闭文件
fclose(pFile);
return fileSize;
}
DWORD CopyFileBufferToImageBuffer(IN LPVOID pFileBuffer, OUT LPVOID* pImageBuffer)
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
LPVOID PfileSectionp = NULL;
LPVOID PImageSectionp = NULL;
//判断是否是有效的MZ标志
if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("不是有效的MZ标志\n");
free(pFileBuffer);
return NULL;
}
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
//判断是否是有效的PE标志
if (*((PDWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志\n");
free(pFileBuffer);
return NULL;
}
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
*pImageBuffer = malloc(pOptionHeader->SizeOfImage);
if (*pImageBuffer == NULL)
{
printf("image申请空间失败\n");
free(*pImageBuffer);
return NULL;
}
memset(*pImageBuffer, 0, pOptionHeader->SizeOfImage);//空间初始化为0
memcpy(*pImageBuffer, pFileBuffer, pOptionHeader->SizeOfHeaders);//复制整个PE头ImageBuffer
//*pImageBuffer =(char*)*pImageBuffer+1024; //改变指针指向位置的算法
printf("pImageBuffer当前位置:%p \n", *pImageBuffer);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);//节目录的起始位置=可选头位置+可选头大小
printf("********************复制节********************\n");
for (size_t i = 0; i < pPEHeader->NumberOfSections; i++)
{
PImageSectionp = (char*)*pImageBuffer + pSectionHeader->VirtualAddress;
printf("复制第%i个节:%p \n", i + 1, PImageSectionp);
//pFileBuffer = (char*)pFileBuffer + pSectionHeader->PointerToRawData;
PfileSectionp = (char*)pFileBuffer + pSectionHeader->PointerToRawData;;
memcpy(PImageSectionp, PfileSectionp, pSectionHeader->SizeOfRawData);//复制整个PE头ImageBuffer
pSectionHeader++;
}
return pOptionHeader->SizeOfImage;
}
DWORD CopyImageBufferToNewBuffer(IN LPVOID pImageBuffer, OUT LPVOID* pNewBuffer)
{
int newBuffersize;
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
LPVOID PnewSectionp = NULL;
LPVOID PImageSectionp = NULL;
//判断是否是有效的MZ标志
if (*((PWORD)pImageBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("不是有效的MZ标志\n");
free(pImageBuffer);
return NULL;
}
pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
//判断是否是有效的PE标志
if (*((PDWORD)((DWORD)pImageBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志\n");
free(pImageBuffer);
return NULL;
}
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pImageBuffer + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
pSectionHeader = pSectionHeader + (pPEHeader->NumberOfSections - 1);//最后一个节的位置
newBuffersize = pSectionHeader->PointerToRawData + pSectionHeader->SizeOfRawData;//通过在文件中最后一个节的位置加上节在文件中对齐的大小就是文件大小,也就是要开辟空间的大小
pSectionHeader = pSectionHeader - (pPEHeader->NumberOfSections - 1);//恢复到一个节的位置
*pNewBuffer = malloc(newBuffersize); //申请新空间,空间大小应该是文件大小
if (*pNewBuffer == NULL)
{
printf("image申请空间失败\n");
free(*pNewBuffer);
return NULL;
}
memset(*pNewBuffer, 0, newBuffersize);//空间初始化为0
memcpy(*pNewBuffer, pImageBuffer, pOptionHeader->SizeOfHeaders);//复制整个PE头到内存空间
printf("********************复制节到文件********************\n");
for (size_t i = 0; i < pPEHeader->NumberOfSections; i++)
{
PImageSectionp = (char*)pImageBuffer + pSectionHeader->VirtualAddress;
printf("复制第%i个节:%p \n", i + 1, PImageSectionp);
//pFileBuffer = (char*)pFileBuffer + pSectionHeader->PointerToRawData;
PnewSectionp = (char*)*pNewBuffer + pSectionHeader->PointerToRawData;;
memcpy(PnewSectionp, PImageSectionp, pSectionHeader->SizeOfRawData);//复制整个PE头ImageBuffer
pSectionHeader++;
}
return newBuffersize;
}
BOOL MemeryTOFile(IN LPVOID pMemBuffer, IN size_t size, OUT LPSTR lpszFile)
{
printf("复制到文件%s,内存地址%p,大小%i\n", lpszFile, pMemBuffer, size);
FILE* fp;
fp = fopen(lpszFile, "wb");
//缓存内容写入到文件
if (fwrite(pMemBuffer, size, 1, fp))
{
printf("存盘成功\n");
}
else
{
printf("存盘失败\n");
}
fclose(fp);
}
DWORD RvaToFileOffset(IN LPVOID pFileBuffer, IN DWORD dwRva)
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
DWORD VirtualOffset;
DWORD foa;
//判断是否是有效的MZ标志
if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("不是有效的MZ标志\n");
free(pFileBuffer);
return NULL;
}
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
//判断是否是有效的PE标志
if (*((PDWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志\n");
free(pFileBuffer);
return NULL;
}
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
//判断这个内存偏移地址是在哪个节
for (size_t i = 0; i < pPEHeader->NumberOfSections; i++)
{
printf("第%i个节的RVA:%p \n", i + 1, pSectionHeader->VirtualAddress);
printf("第%i个节的Mics:%p \n", i + 1, pSectionHeader->Misc.VirtualSize);
if ((dwRva >= pSectionHeader->VirtualAddress) && dwRva <= (pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize))
{
VirtualOffset = dwRva - pSectionHeader->VirtualAddress;
foa = pSectionHeader->PointerToRawData + VirtualOffset;
printf("在第%i个节的VirtualAddress:%p \n", i + 1, pSectionHeader->VirtualAddress);
printf("在第%i个节的Mics:%p \n", i + 1, pSectionHeader->Misc.VirtualSize);
printf("VirtualOffset偏移:%x \n", VirtualOffset);
return foa;
}
pSectionHeader++;
}
return NULL;
}
VOID TestAddCodeIncodeSec()
{
LPVOID pFileBuffer = NULL;
LPVOID pImageBuffer = NULL;
LPVOID pNewBuffer = NULL;
size_t size;
DWORD foa;
PBYTE codeBegin;//添加代码的位置
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
if (!ReadPEFile((char *)FILEPATH, &pFileBuffer))
{
printf("文件读取失败\n");
return;
}
printf("newbuffer开始的位置:%p \n", pNewBuffer);
CopyFileBufferToImageBuffer(pFileBuffer, &pImageBuffer);
pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pImageBuffer + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
//判断是否有足够的空间添加shellCode
if (((pSectionHeader->PointerToRawData) - (pSectionHeader->Misc.VirtualSize)) < SHELLCODELENGTH)
{
printf("代码区空闲区别不够");
free(pFileBuffer);
free(pImageBuffer);
return;
}
//计算出codeBegin代码添加的起始位置
codeBegin = (PBYTE)((DWORD)pImageBuffer + pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize);
memcpy(codeBegin, shellCode, sizeof(shellCode));//复制代码带空白区域
//计算E8后面的硬编码=真实要执行的地址-((模块加载基址imageBase+E8下一条地址距离模块基址的偏移))
DWORD callAddr = MESSAGEBOXADDR - ((pOptionHeader->ImageBase + ((DWORD)codeBegin + 0xD - (DWORD)pImageBuffer)));
*(PDWORD)(codeBegin + 9) = callAddr;//修正E8 后面的硬编码
//计算E9后面的硬编码=真实要执行的地址-((模块加载基址imageBase+E9下一条地址距离模块基址的偏移))
DWORD jmpAddr = (pOptionHeader->ImageBase + pOptionHeader->AddressOfEntryPoint) - ((pOptionHeader->ImageBase + ((DWORD)codeBegin + 0xD + 5 - (DWORD)pImageBuffer)));
*(PDWORD)(codeBegin + 9 + 5) = jmpAddr;//修正E9 后面的硬编码
//修改入口地址
pOptionHeader->AddressOfEntryPoint = (DWORD)codeBegin - (DWORD)pImageBuffer;
size = CopyImageBufferToNewBuffer(pImageBuffer, &pNewBuffer);
printf("newbuffer里的pImageBuffer当前位置:%p \n", pNewBuffer);
MemeryTOFile(pNewBuffer, size, (char *)FILEPATH_NEW);
foa = RvaToFileOffset(pFileBuffer, 8224);
printf("FOA偏移:%x \n", foa);
}
VOID PrintNTHeaders()
{
LPVOID pFileBuffer = NULL;
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
if (!ReadPEFile((char *)FILEPATH, &pFileBuffer))
{
printf("文件读取失败\n");
return;
}
//判断是否是有效的MZ标志
if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("不是有效的MZ标志\n");
free(pFileBuffer);
return;
}
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
//打印DOC头
printf("********************DOC头********************\n");
printf("MZ标志:%x\n", pDosHeader->e_magic);
printf("PE偏移:%x\n", pDosHeader->e_lfanew);
//判断是否是有效的PE标志
if (*((PDWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志\n");
free(pFileBuffer);
return;
}
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
//打印NT头
printf("********************NT头********************\n");
printf("NT:%x\n", pNTHeader->Signature);
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
printf("********************PE头********************\n");
printf("PE:%x\n", pPEHeader->Machine);
printf("节的数量:%x\n", pPEHeader->NumberOfSections);
printf("时间戳:%x\n", pPEHeader->TimeDateStamp);
printf("指向符号表:%x\n", pPEHeader->PointerToSymbolTable);
printf("符号表中的符号数量:%x\n", pPEHeader->NumberOfSymbols);
printf("可选PE头大小SizeOfOptionalHeader:%x\n", pPEHeader->SizeOfOptionalHeader);
printf("文件属性:%x\n", pPEHeader->Characteristics);
//可选PE头
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
printf("********************OPTIOIN_PE头********************\n");
printf("OPTION_PE:%x\n", pOptionHeader->Magic);
printf("文件对齐尺寸:%x\n", pOptionHeader->FileAlignment);
printf("DOS头+PE头+可选PE头+节表大小:%x\n", pOptionHeader->SizeOfHeaders);
printf("内存中文件镜像大小:%x\n", pOptionHeader->SizeOfImage);
//节表
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
printf("********************节表信息********************\n");
for (size_t i = 0; i < pPEHeader->NumberOfSections; i++)
{
printf("------------------节表%i------------------\n", i + 1);
printf("名称:%s\n", pSectionHeader->Name);
printf("真实尺寸:%.8x\n", pSectionHeader->Misc);
printf("RVA地址:%.8x\n", pSectionHeader->VirtualAddress);
printf("文件对齐后尺寸:%.8x\n", pSectionHeader->SizeOfRawData);
printf("文件中偏移:%.8x\n", pSectionHeader->PointerToRawData);
printf("行号表的位置:%.8x\n", pSectionHeader->PointerToLinenumbers);
printf("重定位表个数:%.8x\n", pSectionHeader->NumberOfRelocations);
printf("行号数量:%.8x\n", pSectionHeader->NumberOfLinenumbers);
printf("节属性:%.8x\n", pSectionHeader->Characteristics);
pSectionHeader++;
}
//释放内存
free(pFileBuffer);
}
这部分主要进行了节表相关的讲解,下部分会进行动态链接库导入导出表的讲解
往期推荐
原创 | 2023 CISCN 第十六届全国大学生信息安全竞赛初赛 WriteUp